commonlibsse_ng\rel\module/
module_handle.rs

1// C++ Original code
2// - https://github.com/SARDONYX-forks/CommonLibVR/blob/ng/include/REL/Module.h
3// - load_segments, clear: https://github.com/SARDONYX-forks/CommonLibVR/blob/ng/src/REL/Module.cpp
4// SPDX-FileCopyrightText: (C) 2018 Ryan-rsm-McKenzie
5// SPDX-License-Identifier: MIT
6//
7// SPDX-FileCopyrightText: (C) 2025 SARDONYX
8// SPDX-License-Identifier: Apache-2.0 OR MI
9
10//! Module handling library for Skyrim SE/AE/VR .
11//!
12//! This module provides functionality to interact with loaded modules (executables and DLLs),
13//! extract segment information, and parse NT headers.
14
15use core::ffi::c_void;
16use core::ptr::NonNull;
17
18/// A handle that obtains and holds the address of the surviving dll/exe until the end of program execution.
19///
20/// # undefined behavior
21/// If `Self::new` specifies a dll/exe that does not live until the end of program execution
22#[repr(transparent)]
23#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
24pub struct ModuleHandle(pub(crate) NonNull<c_void>);
25
26unsafe impl Send for ModuleHandle {}
27unsafe impl Sync for ModuleHandle {}
28
29impl Default for ModuleHandle {
30    #[inline]
31    fn default() -> Self {
32        Self::const_default()
33    }
34}
35
36impl ModuleHandle {
37    #[inline]
38    pub(crate) const fn const_default() -> Self {
39        Self(NonNull::dangling())
40    }
41
42    /// Gets the module handle of a module (exe, dll, etc.) that is being loaded by the calling process.
43    ///
44    /// # Example
45    /// ```
46    /// use commonlibsse_ng::rel::module::ModuleHandle;
47    /// use windows::core::h; // `h!` is utf-16 str macro.
48    ///
49    /// let handle = unsafe { ModuleHandle::new(h!("kernel32.dll")) };
50    /// assert!(handle.is_ok());
51    ///
52    /// // If there is no extension, a `.dll` is automatically specified.(This is the behavior of `GetModuleHandleW` function.)
53    /// let handle = unsafe { ModuleHandle::new(h!("kernel32")) };
54    /// assert!(handle.is_ok());
55    /// ```
56    ///
57    /// # Errors
58    /// - Errors if a module is specified that is not loaded by the calling process.
59    /// - If the specified module handle could not be obtained.
60    ///
61    /// # Safety
62    /// It is safe as long as specify a dll/exe that survives the `'static` life time.
63    pub unsafe fn new<H>(module_name: H) -> Result<Self, ModuleHandleError>
64    where
65        H: windows::core::Param<windows::core::PCWSTR>,
66    {
67        use snafu::ResultExt as _;
68        use windows::Win32::System::LibraryLoader::GetModuleHandleW;
69
70        // GetModuleHandleW: https://learn.microsoft.com/windows/win32/api/libloaderapi/nf-libloaderapi-getmodulehandlew
71        let handle =
72            unsafe { GetModuleHandleW(module_name) }.with_context(|_| HandleNotFoundSnafu)?;
73
74        // TODO: size of module(However, it incurs the overhead of a function call.
75        // let _module_size = get_module_size(handle).with_context(|_| HandleNotFoundSnafu)?;
76
77        // If it is null, it is not null because of an error in the previous Result.
78        Ok(Self(unsafe { NonNull::new_unchecked(handle.0) }))
79    }
80
81    /// Attempt to parse NT Header part.
82    ///
83    /// # Errors
84    /// When fail to parse as valid header.
85    pub const fn try_as_nt_header(
86        &self,
87    ) -> Result<&windows::Win32::System::Diagnostics::Debug::IMAGE_NT_HEADERS64, ModuleHandleError>
88    {
89        use windows::Win32::System::Diagnostics::Debug::IMAGE_NT_HEADERS64;
90        use windows::Win32::System::SystemServices::{
91            IMAGE_DOS_HEADER, IMAGE_DOS_SIGNATURE, IMAGE_NT_SIGNATURE,
92        };
93
94        let dos_header = self.0.cast::<IMAGE_DOS_HEADER>();
95
96        let e_lfanew_offset = {
97            let dos_header = unsafe { dos_header.as_ref() };
98            // If it is a valid exe or dll, the first two bytes are the letters `MZ`
99            // (inverted with little endian by u16 and containing 0x5a4d) from the designer's name.
100            let dos_magic = dos_header.e_magic;
101            if dos_magic != IMAGE_DOS_SIGNATURE {
102                return Err(ModuleHandleError::InvalidDosHeaderSignature { actual: dos_magic });
103            }
104
105            dos_header.e_lfanew as usize
106        };
107
108        // The nt_header exists at the position e_lfanew from the start of the dos_header, i.e., the binary data of the exe.
109        let nt_header = unsafe {
110            dos_header
111                .byte_add(e_lfanew_offset) // Be careful not to mistakenly use `.add` or `.offset`.
112                .cast::<IMAGE_NT_HEADERS64>()
113                .as_ref()
114        };
115
116        let nt_signature = nt_header.Signature;
117        if nt_signature == IMAGE_NT_SIGNATURE {
118            Ok(nt_header)
119        } else {
120            Err(ModuleHandleError::InvalidNtHeader64Signature { actual: nt_signature })
121        }
122    }
123}
124
125/// Error types for module handle operations.
126#[derive(Debug, Clone, PartialEq, Eq, snafu::Snafu)]
127pub enum ModuleHandleError {
128    /// Invalid module handle.
129    NullHandle,
130
131    /// Failed to get module handle for '{source}'
132    HandleNotFound { source: windows::core::Error },
133    /// Invalid dos header of this exe/dll. Expected `0x5a4d`, but got `{actual}`
134    InvalidDosHeaderSignature { actual: u16 },
135    /// Invalid NT header64.  Expected `PE\0\0`(0x4550), but got `{actual:X}`
136    InvalidNtHeader64Signature { actual: u32 },
137}
138
139// pub fn get_module_size(handle: windows::Win32::Foundation::HMODULE) -> windows::core::Result<u32> {
140//     use windows::Win32::System::ProcessStatus::GetModuleInformation;
141//     use windows::Win32::System::ProcessStatus::MODULEINFO;
142//     use windows::Win32::System::Threading::GetCurrentProcess;
143
144//     const MODULEINFO_SIZE: u32 = core::mem::size_of::<MODULEINFO>() as u32;
145
146//     let mut module_info = MODULEINFO::default();
147//     unsafe {
148//         GetModuleInformation(GetCurrentProcess(), handle, &mut module_info, MODULEINFO_SIZE)?;
149//     }
150
151//     Ok(module_info.SizeOfImage)
152// }
153
154#[cfg(test)]
155mod tests {
156    use super::*;
157    use windows::core::h;
158
159    #[test]
160    fn test_module_handle_nt_header() {
161        let handle =
162            unsafe { ModuleHandle::new(h!("msvcrt.dll")).unwrap_or_else(|err| panic!("{err}")) };
163        let nt_header = handle.try_as_nt_header().unwrap_or_else(|err| panic!("{err}"));
164        assert_ne!(nt_header.Signature, 0);
165    }
166}